// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <turbo/base/macros.h>
#include <nebula/core/finger_printable.h>
#include <turbo/meta/compare.h>
#include <nebula/types/type_fwd.h>  // IWYU pragma: export
#include <nebula/core/visitor.h>  // IWYU pragma: keep
#include <nebula/types/data_type_layout.h>

namespace nebula {

    /// \brief Base class for all data types
    ///
    /// Data types in this library are all *logical*. They can be expressed as
    /// either a primitive physical type (bytes or bits of some fixed size), a
    /// nested type consisting of other data types, or another data type (e.g. a
    /// timestamp encoded as an int64).
    ///
    /// Simple datatypes may be entirely described by their Type::type id, but
    /// complex datatypes are usually parametric.
    class TURBO_EXPORT DataType : public std::enable_shared_from_this<DataType>,
                                  public detail::FingerPrintAble,
                                  public turbo::EqualityComparable<DataType> {
    public:
        explicit DataType(Type::type id) : detail::FingerPrintAble(), id_(id) {}

        ~DataType() override;

        /// \brief Return whether the types are equal
        ///
        /// Types that are logically convertible from one to another (e.g. List<UInt8>
        /// and Binary) are NOT equal.
        bool equals(const DataType &other, bool check_metadata = false) const;

        /// \brief Return whether the types are equal
        bool equals(const std::shared_ptr<DataType> &other, bool check_metadata = false) const;

        /// \brief Return the child field at index i.
        const std::shared_ptr<Field> &field(int i) const { return children_[i]; }

        /// \brief Return the children fields associated with this type.
        const FieldVector &fields() const { return children_; }

        /// \brief Return the number of children fields associated with this type.
        int num_fields() const { return static_cast<int>(children_.size()); }

        /// \brief Apply the TypeVisitor::Visit() method specialized to the data type
        turbo::Status Accept(TypeVisitor *visitor) const;

        /// \brief A string representation of the type, including any children
        virtual std::string to_string(bool show_metadata = false) const = 0;

        /// \brief Return hash value (excluding metadata in child fields)
        size_t hash() const;

        /// \brief A string name of the type, omitting any child fields
        ///
        /// \since 0.7.0
        virtual std::string name() const = 0;

        /// \brief Return the data type layout.  Children are not included.
        ///
        /// \note Experimental API
        virtual DataTypeLayout layout() const = 0;

        /// \brief Return the type category
        Type::type id() const { return id_; }

        /// \brief Return the type category of the storage type
        virtual Type::type storage_id() const { return id_; }

        /// \brief Returns the type's fixed byte width, if any. Returns -1
        /// for non-fixed-width types, and should only be used for
        /// subclasses of FixedWidthType
        virtual int32_t byte_width() const {
            int32_t num_bits = this->bit_width();
            return num_bits > 0 ? num_bits / 8 : -1;
        }

        /// \brief Returns the type's fixed bit width, if any. Returns -1
        /// for non-fixed-width types, and should only be used for
        /// subclasses of FixedWidthType
        virtual int bit_width() const { return -1; }

        // \brief EXPERIMENTAL: Enable retrieving shared_ptr<DataType> from a const
        // context.
        std::shared_ptr<DataType> get_shared_ptr() const {
            return const_cast<DataType *>(this)->shared_from_this();
        }

    protected:
        // Dummy version that returns a null string (indicating not implemented).
        // Subclasses should override for fast equality checks.
        std::string compute_fingerprint() const override;

        // Generic versions that works for all regular types, nested or not.
        std::string compute_metadata_fingerprint() const override;

        Type::type id_;
        FieldVector children_;

    private:
        TURBO_DISALLOW_COPY_AND_ASSIGN(DataType);
    };

    TURBO_EXPORT
    std::ostream &operator<<(std::ostream &os, const DataType &type);

    /// \brief Return the compatible physical data type
    ///
    /// Some types may have distinct logical meanings but the exact same physical
    /// representation.  For example, TimestampType has Int64Type as a physical
    /// type (defined as TimestampType::PhysicalType).
    ///
    /// The return value is as follows:
    /// - if a `PhysicalType` alias exists in the concrete type class, return
    ///   an instance of `PhysicalType`.
    /// - otherwise, return the input type itself.
    std::shared_ptr<DataType> GetPhysicalType(const std::shared_ptr<DataType> &type);
}  // namespace nebula

